﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace Songhay.Silverlight.Controls
{
    using Songhay.Extensions;

    /// <summary>
    /// A <c>Popup</c> composite of <c>ListBox</c>.
    /// </summary>
    [TemplatePart(Name = "DropDownSummary", Type = typeof(TextBlock))]
    [TemplatePart(Name = "DropDownCommand", Type = typeof(ToggleButton))]
    [TemplatePart(Name = "DropDown", Type = typeof(Popup))]
    [TemplatePart(Name = "InnerListBox", Type = typeof(ListBox))]
    public partial class MultiSelectDropDown : UserControl
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MultiSelectDropDown" /> class.
        /// </summary>
        public MultiSelectDropDown()
        {
            InitializeComponent();

            #region eventing:

            this.LayoutUpdated += (s, args) =>
            {
                this.InnerListBox.Width = this.ActualWidth;
            };

            this.InnerListBox.SelectionChanged += (s, args) =>
            {
                this._shouldGetSelectedItems = false;
                this.SetDropDownSummary();
            };

            #endregion

            this._shouldGetSelectedItems = true;
            this.DropDownSummaryFormat = "{0} items selected";
        }

        #region DisplayMemberPath:

        /// <summary>
        /// Gets or sets the display member path.
        /// </summary>
        /// <value>
        /// The display member path.
        /// </value>
        public string DisplayMemberPath
        {
            get { return (string)this.GetValue(DisplayMemberPathProperty); }
            set { this.SetValue(DisplayMemberPathProperty, value); }
        }

        /// <summary>
        /// Sets the display member path.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        public void SetDisplayMemberPath(string newValue)
        {
            this.InnerListBox.DisplayMemberPath = newValue;
        }

        /// <summary>
        /// The display member path property
        /// </summary>
        public static readonly DependencyProperty DisplayMemberPathProperty =
            DependencyProperty.Register(
                "DisplayMemberPath",
                typeof(string),
                typeof(MultiSelectDropDown),
                new PropertyMetadata(null,
                (s, args) =>
                {
                    var that = s as MultiSelectDropDown;
                    if(that == null) throw new Exception("The expected source is not here.");

                    var newValue = (string)args.NewValue;
                    that.SetDisplayMemberPath(newValue);
                }));

        #endregion

        #region DropDownSummaryFormat:

        /// <summary>
        /// Gets or sets the drop down summary format.
        /// </summary>
        /// <value>
        /// The drop down summary format.
        /// </value>
        public string DropDownSummaryFormat
        {
            get { return (string)this.GetValue(DropDownSummaryFormatProperty); }
            set { this.SetValue(DropDownSummaryFormatProperty, value); }
        }

        /// <summary>
        /// The drop down summary format property
        /// </summary>
        public static readonly DependencyProperty DropDownSummaryFormatProperty =
            DependencyProperty.Register(
                "DropDownSummaryFormat",
                typeof(string),
                typeof(MultiSelectDropDown),
                new PropertyMetadata(null)
            );

        #endregion

        /// <summary>
        /// Occurs when Getting selected items for inner <c>ListBox</c>.
        /// </summary>
        public event EventHandler<OnGettingSelectedItemsEventArgs> GettingSelectedItems;

        #region ItemContainerStyle:

        /// <summary>
        /// Gets or sets the item container style.
        /// </summary>
        /// <value>
        /// The item container style.
        /// </value>
        public Style ItemContainerStyle
        {
            get { return (Style)this.GetValue(ItemContainerStyleProperty); }
            set { this.SetValue(ItemContainerStyleProperty, value); }
        }

        /// <summary>
        /// Sets the item container style.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        public void SetItemContainerStyle(Style newValue)
        {
            this.InnerListBox.ItemContainerStyle = newValue;
        }

        /// <summary>
        /// The item container style property
        /// </summary>
        public static readonly DependencyProperty ItemContainerStyleProperty =
            DependencyProperty.Register(
                "ItemContainerStyle",
                typeof(IEnumerable),
                typeof(MultiSelectDropDown),
                new PropertyMetadata(null, (s, args) =>
                {
                    var that = s as MultiSelectDropDown;
                    if(that == null) throw new Exception("The expected source is not here.");

                    var newValue = (Style)args.NewValue;
                    that.SetItemContainerStyle(newValue);
                })
            );

        #endregion

        #region ItemsSource:

        /// <summary>
        /// Gets or sets the items source.
        /// </summary>
        /// <value>
        /// The items source.
        /// </value>
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)this.GetValue(ItemsSourceProperty); }
            set { this.SetValue(ItemsSourceProperty, value); }
        }

        /// <summary>
        /// Sets the items source.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        public void SetItemsSource(IEnumerable newValue)
        {
            this.InnerListBox.ItemsSource = newValue;
            this.SetDropDownSummary();
        }

        /// <summary>
        /// The items source property.
        /// </summary>
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register(
                "ItemsSource",
                typeof(IEnumerable),
                typeof(MultiSelectDropDown),
                new PropertyMetadata(null, (s, args) =>
                {
                    var that = s as MultiSelectDropDown;
                    if(that == null) throw new Exception("The expected source is not here.");

                    var newValue = (IEnumerable)args.NewValue;
                    that.SetItemsSource(newValue);
                })
            );

        #endregion

        #region SelectedItems:

        /// <summary>
        /// Gets or sets the selected items.
        /// </summary>
        /// <value>
        /// The selected items.
        /// </value>
        public IList SelectedItems
        {
            get { return (IList)this.GetValue(SelectedItemsProperty); }
            set { this.SetValue(SelectedItemsProperty, value); }
        }

        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register(
                "SelectedItems",
                typeof(IList),
                typeof(MultiSelectDropDown),
                new PropertyMetadata(null)
                );

        #endregion

        #region SelectedValuePath:

        /// <summary>
        /// Gets or sets the selected value path.
        /// </summary>
        /// <value>
        /// The selected value path.
        /// </value>
        public string SelectedValuePath
        {
            get { return (string)this.GetValue(SelectedValuePathProperty); }
            set { this.SetValue(SelectedValuePathProperty, value); }
        }

        /// <summary>
        /// Sets the selected value path.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        public void SetSelectedValuePath(string newValue)
        {
            this.InnerListBox.SelectedValuePath = newValue;
        }

        /// <summary>
        /// The selected value path property
        /// </summary>
        public static readonly DependencyProperty SelectedValuePathProperty =
            DependencyProperty.Register(
                "SelectedValuePath",
                typeof(string),
                typeof(MultiSelectDropDown),
                new PropertyMetadata(null, (s, args) =>
                {
                    var that = s as MultiSelectDropDown;
                    if(that == null) throw new Exception("The expected source is not here.");

                    var newValue = (string)args.NewValue;
                    that.SetSelectedValuePath(newValue);
                }));

        #endregion

        IEnumerable<object> GetSelectedItems()
        {
            if(this.GettingSelectedItems != null)
            {
                var e = new OnGettingSelectedItemsEventArgs(this.InnerListBox.Items);
                this.GettingSelectedItems(this, e);
                if(e.SelectedItems != null) return e.SelectedItems;
            }

            return this.InnerListBox.Items
                .OfType<ListBoxItem>()
                .Where(i => i.IsSelected == true);
        }

        void SetDropDownSummary()
        {
            this.Dispatcher.BeginInvoke(() =>
            {
                if(this._shouldGetSelectedItems)
                    this.GetSelectedItems().ForEachInEnumerable(i =>
                        {
                            if(!this.InnerListBox.SelectedItems.Contains(i))
                                this.InnerListBox.SelectedItems.Add(i);
                        });

                this.SelectedItems = this.InnerListBox.SelectedItems;
                var count = this.InnerListBox.SelectedItems.Count;
                this.DropDownSummary.Text = string.Format(this.DropDownSummaryFormat, count);
            });
        }

        bool _shouldGetSelectedItems;
    }
}
